import {trim} from './lib/tools'
import {HSqlObjectT, WhereObjectT} from './types'

const symbol = ['HSqlBuilder-4fes94f64F564FE4G564ef6sd43fe465']

const notOperator = ['!=', 'IS NOT']

const updateSymbles = ['+', '-', '*', '/']

const relationSymbol = ['or', 'OR', 'and', 'AND']

/**
 * 检查symbol是否本地的。
 * 有可能把symbol返回外面后，外面会深拷贝它，那直接判断会认为这不是我们的symbol
 * */
const checkSymbol = (inputSymbol: any) => {
  return (
    inputSymbol && (inputSymbol === symbol || inputSymbol?.[0] === symbol[0])
  )
}

/**
 * 用`号括起入参，可多次调用该方法，不会重复加
 *
 * k可以是：
 * a.b        某表的某字段
 * count(*)   带括号的函数
 * count(*) + sum(a)   带运算符（+-/*%）
 * `a`.`b`    已有引号的
 * a.b as c   带 as 的
 *
 * @param {string} k
 * @param {string} [defTable] 默认表，就是如果字段没指定是哪个表的，可以通过这个参数给字段加一个
 * */
export const quotK = (k: string, defTable?: string): string => {
  if (!k) {
    return ''
  }
  // 判断是否需要拆开转义
  if (k.includes(' as ')) {
    return k
      .replace(/\s+/g, ' ')
      .split(' as ')
      .map(v => quotK(v))
      .join(' as ')
  }
  // 已括起、函数、运算符、纯数字，都不转义
  if (/[`(+\-*/%]/.test(k) || /^\d+$/.test(k)) {
    return k
  }
  if (k.includes('.')) {
    return k.split('.').map(quotBase).join('.')
  }
  return (defTable ? `${quotBase(defTable)}.` : '') + quotBase(k)
}

const quotBase = (k: string) => `\`${k}\``

// 生成where语句,where支持的数据类型更多
export function whereMain(where: any, defTable?: string) {
  if (typeof where === 'string') {
    return [where, []]
  }

  // 直接传 where里的sql语句和对应的值进来
  if (Array.isArray(where)) {
    return where
  }

  return breakObjectWhere(where, 'AND', defTable)
}

/**
 * 仅仅生成update中 set 后的参数
 * 返回方式跟上面的不一样
 * @param o 对象形式的参数
 * */
export function updateSet(o: WhereObjectT) {
  const sqls: string[] = []
  const vals: (string | number)[] = []
  Object.entries(o).forEach(([k, v]) => {
    const ks = k.split('#')
    k = quotK(ks[0])
    switch (ks[1]) {
      case 'function': {
        if (k) {
          sqls.push(`${k} = ${v}`)
        } else {
          sqls.push(v as string)
        }
        return
      }
      case 'sql': {
        sqls.push(v[0])
        if (v[1]) {
          vals.push(...v[1])
        }
        return
      }
      default: {
        switch (typeof v) {
          case 'string':
          case 'number':
            sqls.push(`${k} = ?`)
            vals.push(v)
            break
          case 'boolean':
            sqls.push(`${k} = ?`)
            vals.push(v ? 1 : 0)
            break
          case 'object': {
            if (v) {
              // 一个值下面只支持一种加减算法，否则又加又乘很奇怪
              updateSymbles.some(sym => {
                if (v[sym]) {
                  sqls.push(`${k} = ${k} ${sym} ?`)
                  vals.push(v[sym] as string)
                  return true
                }
              })
            } else {
              sqls.push(`${k} = null`)
            }
            break
          }
          case 'undefined':
            break
          default:
            throw new Error(`不支持这种传值方式：${JSON.stringify({[k]: v})}`)
        }
      }
    }
  })
  return [sqls, vals]
}

/**
 * @param {string | Array} leftJoins
 * @return string
 * */
export function leftJoin(leftJoins: string | string[]) {
  if (!leftJoins) {
    return ''
  }
  if (typeof leftJoins === 'string') {
    return `LEFT JOIN ${leftJoins}`
  }
  return (leftJoins.length ? ' LEFT JOIN ' : '') + leftJoins.join(' LEFT JOIN ')
}

export function returnData(sql: string, values: any[]): HSqlObjectT {
  const data: HSqlObjectT = {
    sql,
    values,
    symbol: symbol,
  }
  return data
}

/**
 * 分解where对象，让whereMain支持特殊key，特殊key有：and、or、function
 * @param where 要分解的对象
 * @param andOr 对象内各个字段之间是 and 关系还是 or 关系
 * @param defTable 对象内的 key 要不要加上表名前缀
 * */
function breakObjectWhere(where: any, andOr = 'AND', defTable?: string): any[] {
  const sqls: string[] = []
  const vals: (string | number)[] = []
  Object.entries(where).forEach(([k, v]) => {
    const ks = k.split('#')
    if (ks[1] === 'function') {
      // function模式
      if (typeof v === 'string') {
        sqls.push(v)
      } else if (Array.isArray(v)) {
        sqls.push(v.splice(0, 1)[0])
        vals.push(...v)
      } else {
        throw new Error('function模式只支持字符串或数组')
      }
      return
    }
    const key = trim(ks[0])
    if (relationSymbol.includes(key)) {
      if (!v) {
        return
      }
      const [ss, vs] = breakObjectWhere(v, key.toUpperCase(), defTable)
      sqls.push(`(${ss})`)
      vals.push(...vs)
    } else {
      const andOr = trim(ks[1] || 'AND').toUpperCase()
      const [ss, vs] = whereOne(
        quotK(key, defTable),
        '=',
        v,
        ['AND', 'OR'].includes(andOr) ? andOr : 'AND'
      )
      // console.log(111, where)
      sqls.push(...ss)
      vals.push(...vs)
    }
  })
  return [sqls.join(` ${andOr} `), vals]
}

function whereOne(
  key: string,
  operator: string,
  value: any | any[],
  andOr = 'AND'
) {
  const sqls = []
  const vals = []
  operator = operator === '!' ? '!=' : operator === 'NOT' ? 'IS NOT' : operator
  switch (typeof value) {
    case 'string':
    case 'number':
      sqls.push(`${key} ${operator} ?`)
      vals.push(value)
      break
    case 'boolean':
      sqls.push(`${key} ${operator} ?`)
      vals.push(value ? 1 : 0)
      break
    case 'object': {
      if (!value) {
        // NOTICE: 不能严格等于
        let op = 'IS'
        if (notOperator.includes(operator)) {
          op = 'IS NOT'
        }
        sqls.push(`${key} ${op} NULL`)
        break
      }
      if (checkSymbol(value.symbol)) {
        sqls.push(`${key} ${operator} (${value.sql})`)
        if (value.values && value.values.length) {
          vals.push(...value.values)
        }
        break
      }
      if (value instanceof Date) {
        const str = `${value.getFullYear()}-${
          value.getMonth() + 1
        }-${value.getDate()} ${value.getHours()}:${value.getMinutes()}:${value.getSeconds()}`

        sqls.push(`${key} ${operator} "${str}"`)
        break
      }
      if (value instanceof Array) {
        let op = 'NOT IN'
        if (!notOperator.includes(operator)) {
          op = 'IN'
        }

        sqls.push(
          `${key} ${op} (${new Array(value.length).fill('?').join(',')})`
        )
        vals.push(...value)
        break
      }

      // value是对象，且对象中的key是操作符，如： {'!=':'','not':[1,2,3]}
      const objSqls: string[] = []
      Object.entries(value).forEach(([operator, vv]) => {
        operator = trim(operator.split('#')[0])
        const [ss, vs] = whereOne(key, operator, vv)
        objSqls.push(...ss)
        vals.push(...vs)
      })
      const s = objSqls.join(` ${andOr} `)
      sqls.push(objSqls.length > 1 ? `(${s})` : s)
      break
    }
    case 'undefined':
      break
    default:
      throw new Error(`不支持这种传值方式：${JSON.stringify({[key]: value})}`)
  }
  return [sqls, vals]
}
